package com.sloan.community.service;

import com.sloan.community.common.Constant;
import com.sloan.community.common.query.SearchQuery;
import com.sloan.community.common.vo.NotificationVO;
import com.sloan.community.common.vo.PageVo;
import com.sloan.community.exception.CommunityException;
import com.sloan.community.po.Article;
import com.sloan.community.po.Comment;
import com.sloan.community.po.Notification;
import com.sloan.community.po.User;
import com.sloan.community.repository.ArticleRepository;
import com.sloan.community.repository.CommentRepository;
import com.sloan.community.repository.NotificationRepository;
import com.sloan.community.repository.UserRepository;
import com.sloan.community.util.TextUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple5;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

import static com.sloan.community.common.Constant.*;
import static com.sloan.community.common.Constant.CommentStatus.HIDE;
import static com.sloan.community.common.Constant.CommentStatus.SHOW;
import static com.sloan.community.common.Constant.NoticeStatus.READ;
import static com.sloan.community.common.Constant.NoticeStatus.UN_READ;

@Slf4j
@Service
@RequiredArgsConstructor(onConstructor_ = {@Lazy})
public class NotificationService {

    private final NotificationRepository notificationRepository;
    private final ArticleRepository articleRepository;
    private final UserRepository userRepository;
    private final CommentRepository commentRepository;


    public Mono<PageVo<NotificationVO>> noticePageList(SearchQuery query, Long userId) {
        Integer current = query.getCurrent();
        Integer size = query.getSize();
        PageRequest pageRequest = PageRequest.of(current - 1, size)
                .withSort(Sort.by(Sort.Order.asc("status"),
                        Sort.Order.desc("created_time")));
        Flux<Notification> notificationFlux = notificationRepository.findByToUserId(userId, pageRequest)
                .cache();

        Mono<Long> countMono = notificationRepository.countByToUserId(userId);

        Flux<Long> commentIdFlux = notificationFlux.map(Notification::getCommentId).distinct();
        Flux<Long> articleIdFlux = notificationFlux.map(Notification::getArticleId).distinct();
        Flux<Long> fromUserIdFlux = notificationFlux.map(Notification::getFromUserId).distinct();
        Flux<Long> toUserIdFlux = notificationFlux.map(Notification::getToUserId).distinct();

        Mono<Map<Long, Comment>> commentMapMono = commentRepository.findAllById(commentIdFlux)
                .collectMap(Comment::getId, Function.identity());

        Flux<Article> articleFlux = articleRepository.findAllById(articleIdFlux).cache();
        Mono<Map<Long, Article>> articleMapMono = articleFlux.collectMap(Article::getId, Function.identity());
        Flux<Long> articleUserIdFlux = articleFlux.map(Article::getUserId).distinct();

        Mono<Map<Long, User>> userMapMono = Mono.zip(fromUserIdFlux.collectList(),
                        toUserIdFlux.collectList(), articleUserIdFlux.collectList())
                .flatMap(tuple -> {
                    Set<Long> userIds = new HashSet<>(tuple.getT1());
                    userIds.addAll(tuple.getT2());
                    userIds.addAll(tuple.getT3());
                    return userRepository.findByIdIsIn(userIds)
                            .collectMap(User::getId, Function.identity());
                });

        return Mono.zip(notificationFlux.collectList(), articleMapMono,
                        commentMapMono, userMapMono, countMono)
                .map(tuple -> new PageVo<NotificationVO>(tuple.getT5(), transferNotificationVO(tuple),
                        current, size, 5, false));
    }

    private static List<NotificationVO> transferNotificationVO(Tuple5<List<Notification>,
            Map<Long, Article>, Map<Long, Comment>, Map<Long, User>, Long> tuple) {

        Map<Long, Article> articleMap = tuple.getT2();
        Map<Long, Comment> commentMap = tuple.getT3();
        Map<Long, User> userMap = tuple.getT4();

        return tuple.getT1().stream()
                .map(notice -> {
                    Article article = articleMap.get(notice.getArticleId());
                    Comment comment = commentMap.get(notice.getCommentId());
                    User fromUser = userMap.get(notice.getFromUserId());
                    User toUser = userMap.get(notice.getToUserId());

                    NotificationVO noticeVo = new NotificationVO();
                    noticeVo.setNotificationId(notice.getId());
                    noticeVo.setFromUserId(notice.getFromUserId());
                    noticeVo.setFromNickName(fromUser.getNickname());
                    noticeVo.setFromUsername(fromUser.getUsername());
                    noticeVo.setFromAvatarUrl(fromUser.getAvatarUrl());
                    noticeVo.setFromAddress(fromUser.getAddress());
                    noticeVo.setToUserId(toUser.getId());
                    noticeVo.setToNickName(toUser.getNickname());
                    noticeVo.setToUsername(toUser.getUsername());
                    noticeVo.setToAvatarUrl(toUser.getAvatarUrl());
                    noticeVo.setToAddress(toUser.getAddress());

                    noticeVo.setArticleId(notice.getArticleId());
                    noticeVo.setTitle(article.getTitle());
                    noticeVo.setArticleUsername(userMap.get(article.getUserId()).getUsername());
                    noticeVo.setCommentId(notice.getCommentId());
                    noticeVo.setStatus(notice.getStatus());
                    noticeVo.setNoDel(comment.getStatus());
                    if (HIDE.getCode().equals(noticeVo.getNoDel())) {
                        noticeVo.setContent(NOTICE_DEL_MSG);
                    }
                    if (SHOW.getCode().equals(noticeVo.getNoDel())) {
                        noticeVo.setContent(TextUtils.handleImgHtmlToText(comment.getContent(), NOTICE_MSG_LEN));
                    }
                    noticeVo.setType(notice.getType());
                    noticeVo.setTypeMsg(Constant.NoticeType.getByCode(notice.getType()).getMsg());
                    noticeVo.setCreatedTime(notice.getCreatedTime());
                    return noticeVo;
                }).toList();
    }

    public Mono<Long> noticeStatus(Long notificationId, Integer status, Long userId) {
        Boolean flag = status.equals(READ.getCode()) ? Boolean.TRUE : Boolean.FALSE;
        return notificationRepository.findById(notificationId)
                .switchIfEmpty(Mono.error(CommunityException.of("非法错误")))
                .<Notification>handle((notice, sink) -> {
                    if (!notice.getToUserId().equals(userId)) {
                        sink.error(CommunityException.of("非法错误"));
                        return;
                    }
                    if (notice.getStatus().equals(flag)) {
                        return;
                    }
                    sink.next(notice);
                })
                .then(notificationRepository.noticeStatus(notificationId, status))
                .thenReturn(notificationId);
    }

    public Mono<Long> unread(Long userId) {
        return notificationRepository.countByToUserIdAndStatus(userId, UN_READ.getCode());
    }
}
